package gameengine.map;

import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

import enumm.GROUND;

public final class Map implements MapInstance {
    private static final int NUMBER_EDGES = 4;

    private transient int xSize, ySize;

    private final Field[] field;

    private boolean ready;

    private transient Observed obs;
    private transient List<Observer> observer;

    public Map(final int xSizeParam, final int ySizeParam, final Field[] array) {
        super();
        this.obs = new Observed();
        this.xSize = xSizeParam;
        this.ySize = ySizeParam;
        this.observer = new ArrayList<Observer>();
        this.field = array.clone();
        this.ready = true;
    }

    public void fillWithGrass() {
        final Field grassField = new Field(GROUND.GRASS, 0);
        for (int i = 1; i <= this.xSize; i++) {
            for (int j = 1; j <= this.ySize; j++) {
                setField(i, j, grassField);
            }
        }
    }

    public void fillWithWater() {
        final Field waterField = new Field(GROUND.WATER, 0);
        for (int i = 1; i <= this.xSize; i++) {
            for (int j = 1; j <= this.ySize; j++) {
                setField(i, j, waterField);
            }
        }

        initWater();
    }

    public void initWater() {
        for (int i = 1; i <= this.xSize; i++) {
            for (int j = 1; j <= this.ySize; j++) {
                final Field scannedField = getField(i, j);
                if (scannedField != null && scannedField.getGround() == GROUND.WATER) {
                    final int[] border = initBanks(i, j);
                    final boolean[] corner = initCorners(border, i, j);
                    scannedField.initWater(border, corner);
                }
            }
        }
    }

    private boolean[] initCorners(final int[] borders, final int xCoordinate, final int yCoordinate) {
        final boolean[] corners = {false, false, false, false};

        int index = 0;

        // TODO check

        initOneCorner(borders, xCoordinate + 1, yCoordinate - 1, index++);
        initOneCorner(borders, xCoordinate + 1, yCoordinate + 1, index++);
        initOneCorner(borders, xCoordinate - 1, yCoordinate + 1, index++);
        initOneCorner(borders, xCoordinate - 1, yCoordinate - 1, index);

        return corners;
    }

    private boolean initOneCorner(final int[] borders, final int xCoordinate, final int yCoordinate, final int index) {
        if (borders[index] == 0 && borders[(index + 1) % NUMBER_EDGES] == 0) {
            final Field waterField = getField(xCoordinate, yCoordinate);
            if (waterField != null && waterField.getGround() != GROUND.WATER) {
                return true;
            }
        }

        return false;
    }

    private int[] initBanks(final int xCoordinate, final int yCoordinate) {
        final Field[] fields = new Field[NUMBER_EDGES];
        final int[] borders = {0, 0, 0, 0};
        int index = 0;

        fields[index++] = getField(xCoordinate, yCoordinate - 1);
        fields[index++] = getField(xCoordinate + 1, yCoordinate);
        fields[index++] = getField(xCoordinate, yCoordinate + 1);
        fields[index] = getField(xCoordinate - 1, yCoordinate);

        for (int i = 0; i < fields.length; i++) {
            if (fields[i] == null) {
                borders[i] = 0;
            } else {
                final GROUND ground = fields[i].getGround();
                if (ground == GROUND.WATER) {
                    borders[i] = 0;
                } else {
                    borders[i] = 1;
                }
            }
        }

        return borders;
    }

    public boolean setField(final int xCoordinate, final int yCoordinate, final Field newField) {
        boolean returnVal;
        if (xCoordinate > this.xSize || yCoordinate > this.ySize || xCoordinate < 1 || yCoordinate < 1) {
            returnVal = false;
        } else {
            final Field clonedField = (Field) newField.clone();
            clonedField.setCoordinates(xCoordinate, yCoordinate);
            for (final Observer o : this.observer) {
                clonedField.addObserver(o);
            }

            this.field[((yCoordinate - 1) * this.xSize) + (xCoordinate - 1)] = clonedField;
            initWater();
            this.obs.update(new Point(xCoordinate, yCoordinate));
            returnVal = true;
        }
        return returnVal;
    }

    public Field getField(final int xCoordinate, final int yCoordinate) {
        if (xCoordinate > this.xSize || yCoordinate > this.ySize || xCoordinate < 1 || yCoordinate < 1) {
            return null;
        } else {
            return this.field[((yCoordinate - 1) * this.xSize) + (xCoordinate - 1)];
        }
    }

    public void moveMent() {
        for (int i = 0; i < this.field.length; i++) {
            if (this.field[i].getUnit() != null) {
                this.field[i].getUnit().hasMoved(0);
            }
        }
    }

    public void addObserver(final Observer observe) {
        this.obs.addObserver(observe);
        this.observer.add(observe);
        for (int i = 0; i < this.field.length; i++) {
            this.field[i].addObserver(observe);
        }
    }

    public boolean isReady() {
        return this.ready;
    }

    public void setReady(final boolean readyState) {
        this.ready = readyState;
    }

    public int getXSize() {
        return this.xSize;
    }

    public int getYSize() {
        return this.ySize;
    }

    public void endRound() {
        for (int i = 0; i < this.field.length; i++) {
            if (this.field[i].getUnit() != null) {
                this.field[i].getUnit().reset();
            }
        }
    }

    private static final class Observed extends Observable {
        private Observed() {
            super();
        }

        private void update(final Point p) {
            setChanged();
            notifyObservers(p);
        }
    }

    public void update(final Observable arg0, final Object arg1) {
        if (arg1 instanceof Action) {
            final Action a = (Action) arg1;
            a.runAction(this);
        }
    }
}
